///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_TRAITS_TUPLE_H_
#define _BUOLA_TRAITS_TUPLE_H_

#include <buola/buola.h>

#include <tuple>

namespace buola {

//returns true if type is a tuple
template<typename tType>
struct MIsTuple : public std::false_type
{};

template<typename... tElem>
struct MIsTuple<std::tuple<tElem...>> : public std::true_type
{};

//returns true if type is in tuple
template<typename tElem,typename tTuple>
struct MTypeInTuple : public std::false_type
{};

template<typename tElem,typename... tRest>
struct MTypeInTuple<tElem,std::tuple<tElem,tRest...>> : public std::true_type
{};

template<typename tElem,typename tFirst,typename... tRest>
struct MTypeInTuple<tElem,std::tuple<tFirst,tRest...>>
{
    static const bool value=MTypeInTuple<tElem,std::tuple<tRest...>>::value;
};

//returns the position of type in the tuple, or -1 if it is not there
template<typename tElem,typename tTuple,int pStart=0>
struct MIndexInTuple
{
    static const int value=-1;
};

template<typename tElem,typename... tRest,int pStart>
struct MIndexInTuple<tElem,std::tuple<tElem,tRest...>,pStart>
{
    static const int value=pStart;
};

template<typename tElem,typename tFirst,typename... tRest,int pStart>
struct MIndexInTuple<tElem,std::tuple<tFirst,tRest...>,pStart>
{
    static const int value=MIndexInTuple<tElem,std::tuple<tRest...>,pStart+1>::value;
};

//returns true if all elements of the tuple are identical
template<typename tTuple>
struct MAllEqualTuple
{
    static const bool value=false;
};

template<typename tType>
struct MAllEqualTuple<std::tuple<tType>>
{
    static const bool value=true;
};

template<typename tType,typename... tRest>
struct MAllEqualTuple<std::tuple<tType,tType,tRest...>>
{
    static const bool value=MAllEqualTuple<std::tuple<tType,tRest...>>::value;
};

//inserts the type to the beginning of the tuple, unless it is UUnused
template<typename tElem,typename tTuple>
struct MInsertInTuple;

template<typename... tElems>
struct MInsertInTuple<UUnused,std::tuple<tElems...>>
{
    typedef std::tuple<tElems...> type;
};

template<typename tElem,typename... tElems>
struct MInsertInTuple<tElem,std::tuple<tElems...>>
{
    typedef std::tuple<tElem,tElems...> type;
};

//inserts the type to the beginning of the tuple, unless it is UUnused or it is already there
template<typename tElem,typename tTuple>
struct MInsertInUniqueTuple
{
    typedef tTuple type;
};

template<typename... tElems>
struct MInsertInUniqueTuple<UUnused,std::tuple<tElems...>>
{
    typedef std::tuple<tElems...> type;
};

template<typename tElem,typename... tElems>
struct MInsertInUniqueTuple<tElem,std::tuple<tElems...>>
{
    typedef typename std::conditional<MTypeInTuple<tElem,std::tuple<tElems...>>::value,
                                      std::tuple<tElems...>,
                                      std::tuple<tElem,tElems...>>::type type;
};

//this class creates a tuple from all elements in tAll, removing UUnused elements

template<typename... tAll>
struct MCreateTuple;

template<>
struct MCreateTuple<>
{
    typedef std::tuple<> type;
};

template<typename tFirst,typename... tRest>
struct MCreateTuple<tFirst,tRest...>
{
    typedef typename MInsertInTuple<tFirst,typename MCreateTuple<tRest...>::type>::type type;
};

//this class creates a tuple from all elements in tAll, removing UUnused elements
//and elements that are repeated

template<typename... tAll>
struct MCreateUniqueTuple;

template<>
struct MCreateUniqueTuple<>
{
    typedef std::tuple<> type;
};

template<typename tFirst,typename... tRest>
struct MCreateUniqueTuple<tFirst,tRest...>
{
    typedef typename MInsertInUniqueTuple<tFirst,
                                    typename MCreateUniqueTuple<tRest...>::type>::type type;
};

//this class collapses a tuple in the following way:
// - if the tuple is empty, returns UUnused
// - if the tuple has a single element, returns the element
// - if all the elements in the tuple are identical, returns a vector of that element
// - otherwise, returns the tuple itself

struct MCollapseTypeEmpty {};
struct MCollapseTypeSingle {};
struct MCollapseTypeVector {};
struct MCollapseTypeNoCollapse {};

template<typename tTuple>
struct MCollapseTuple;

template<>
struct MCollapseTuple<std::tuple<>>
{
    typedef UUnused type;
    
    typedef MCollapseTypeEmpty tCollapseType;
};

template<typename tA>
struct MCollapseTuple<std::tuple<tA>>
{
    typedef tA type;

    typedef MCollapseTypeSingle tCollapseType;
};

template<typename tFirst,typename... tElem>
struct MCollapseTuple<std::tuple<tFirst,tElem...>>
{
    typedef typename std::conditional<MAllEqualTuple<std::tuple<tFirst,tElem...>>::value,
                                      std::vector<tFirst>,
                                      std::tuple<tFirst,tElem...>>::type type;

    typedef typename std::conditional<MAllEqualTuple<std::tuple<tFirst,tElem...>>::value,
                                      MCollapseTypeVector,
                                      MCollapseTypeNoCollapse>::type tCollapseType;

};

//returns the element of type tType in the tuple
template<typename tType,typename... tElems>
inline tType &get_elem_of_type(std::tuple<tElems...> &pTuple)
{
    return std::get<MIndexInTuple<tType,std::tuple<tElems...>>::value>(pTuple);
}

template<typename tType>
inline tType &get_elem_of_type(tType &pType)
{
    return pType;
}

template<typename tType,typename tType2,OEnableIf<std::is_same<tType,UUnused>> vDummy=1>
inline UUnused &get_elem_of_type(tType2 &pType)
{
    return *((UUnused*)nullptr);
}

template<typename tType>
inline UUnused &get_elem_of_type(UUnused &pUnused)
{
    return pUnused;
}

//template list of indices. Useful to implement tuple unpacking
template<std::size_t...> struct MIndexTuple{}; 

template<std::size_t N, typename tIndexTuple=MIndexTuple<>>
struct MMakeIndices; 

template<std::size_t N, std::size_t... pIndices> 
struct MMakeIndices<N,MIndexTuple<pIndices...>> 
{ 
    typedef typename MMakeIndices<N-1,MIndexTuple<N-1,pIndices...>>::type type; 
}; 

template<std::size_t... pIndices> 
struct MMakeIndices<0,MIndexTuple<pIndices...>> 
{ 
    typedef MIndexTuple<pIndices...> type; 
}; 

template<typename... tArgs>
using OIndices=typename MMakeIndices<sizeof...(tArgs)>::type;

template<typename... tTypes>
struct MTypeList{};

template<typename tType,typename tList>
struct MIndexInTypeList;

template<typename tType,typename... tTypes>
struct MIndexInTypeList<tType,MTypeList<tTypes...>> : public MIndexInTuple<tType,std::tuple<tTypes...>>
{};

/*namespace buola*/ }

#endif
